package baseFx.common.utils;

import baseFx.common.Action1T;
import baseFx.common.Stream;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class HttpUtils {
    private static final String LINE = "\r\n";

    public static String upload(String url, Map<String, String> param, List<HttpFile> files) {
        String boundary = "---------------------------" + System.currentTimeMillis();
        HttpURLConnection connection = rawPost(url, null, conn -> {
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            try {
                OutputStream out = new DataOutputStream(conn.getOutputStream());
                if (param != null && param.size() > 0) {
                    StringBuilder buf = new StringBuilder();
                    for (String key : param.keySet()) {
                        buf.setLength(0);
                        buf.append(LINE).append("--").append(boundary).append(LINE);
                        buf.append("Content-Disposition: form-data; name=\"" + key + "\"");
                        buf.append(LINE).append(LINE);
                        buf.append(IfUtils.firstNotNull(param.get(key), ""));
                    }
                    out.write(buf.toString().getBytes());
                }
                if (files != null && files.size() > 0) {
                    StringBuilder buf = new StringBuilder();
                    for (HttpFile file : files) {
                        buf.setLength(0);
                        buf.append(LINE).append("--").append(boundary).append(LINE);
                        buf.append("Content-Disposition: form-data; name=\"" + file.paramName + "\"; filename=\"" + file.fileName + "\"");
                        buf.append(LINE);
                        buf.append("Content-Type:" + file.mimeType());
                        buf.append(LINE).append(LINE);
                        out.write(buf.toString().getBytes());
                        try (DataInputStream in = new DataInputStream(file.fileStream)) {
                            int len;
                            byte[] data = new byte[4096];
                            while ((len = in.read(data)) != -1) {
                                out.write(data, 0, len);
                            }
                        }
                    }
                }
                out.write((LINE + "--" + boundary + "--" + LINE).getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                throw new RuntimeException("写入失败", e);
            }
        });
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            }
            throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
        } catch (IOException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static String post(String url, String body, Action1T<HttpURLConnection> before) {
        HttpURLConnection connection = rawPost(url, body, before);
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            }
            throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
        } catch (IOException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static String post(String url, String body) {
        return post(url, body, null);
    }

    public static String post(String url, Map<String, Object> body, Action1T<HttpURLConnection> before) {
        return post(url, UrlUtils.join(body, false), before);
    }

    public static String post(String url, Map<String, Object> body) {
        return post(url, body, null);
    }

    public static String get(String url, Action1T<HttpURLConnection> before) {
        HttpURLConnection connection = rawGet(url, before);
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            }
            throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
        } catch (IOException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static String get(String url) {
        return get(url, null);
    }

    public static HttpURLConnection rawPost(String url, String body, Action1T<HttpURLConnection> before) {
        return httpTo("POST", url, body, before);
    }

    public static HttpURLConnection rawGet(String url, Action1T<HttpURLConnection> before) {
        return httpTo("GET", url, null, before);
    }

    public static HttpURLConnection rawGet(String url) {
        return rawGet(url, null);
    }

    private static HttpURLConnection httpTo(String method, String url, String body, Action1T<HttpURLConnection> before) {
        try {
            URLConnection conn = new URL(url).openConnection();
            if (conn instanceof HttpsURLConnection) {
                HttpsURLConnection https = ((HttpsURLConnection) conn);
                https.setSSLSocketFactory(new MySSLSocketFactory());
                https.setHostnameVerifier(new TrustAnyHostnameVerifier());
            }
            HttpURLConnection connection = (HttpURLConnection) conn;
            connection.setInstanceFollowRedirects(true);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
            if (body != null) {
                connection.setRequestProperty("content-Type", "application/x-www-form-urlencoded");
                connection.setRequestProperty("content-length", body.length() + "");
                if (before != null) {
                    before.execute(connection);
                }
                try (OutputStream stream = connection.getOutputStream();
                     PrintWriter writer = new PrintWriter(stream)) {
                    writer.write(body);
                    writer.flush();
                }
            } else if (before != null) {
                before.execute(connection);
            }
            return connection;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private static class MySSLSocketFactory extends SSLSocketFactory {
        private static String[] cipherSuites;

        private static SSLContext sslContext = null;

        static {
            try {
                Security.setProperty("jdk.tls.disabledAlgorithms", "SSLv3, DH keySize < 768");
                String[] ciphers = SSLContext.getDefault().getSocketFactory().getSupportedCipherSuites();
                List<String> list = Stream.create(ciphers).toList();
                list.add("SSL_RSA_WITH_RC4_128_SHA");
                cipherSuites = list.toArray(new String[0]);

                sslContext = SSLContext.getInstance("TLSv1.2");
                sslContext.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 设置算法套
        private void setSSLParams(SSLSocket sslSocket) {
            sslSocket.setUseClientMode(true);
            sslSocket.setEnabledCipherSuites(cipherSuites);
        }

        private static SSLContext getSslContext() {
            if (sslContext == null) {
                //SSLContext.setDefault(sslContext);
            }
            return sslContext;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return cipherSuites;
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return cipherSuites;
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean b) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(socket, host, port, b);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port, localHost, localPort);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(address, port, localAddress, localPort);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        private static class DefaultTrustManager implements X509TrustManager {

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }
    }

    public static class HttpFile {
        public String paramName;
        public String fileName;
        public String contentType;
        public InputStream fileStream;

        public String mimeType() {
            if (StringUtils.hasValue(contentType)) {
                return contentType;
            }
            String ext = FileUtils.getExt(fileName).toLowerCase();
            switch (ext) {
                case ".txt":
                case ".csv":
                case ".json":
                case ".log":
                    return "text/plain";
                case ".bmp":
                    return "image/x-ms-bmp";
                case ".jpg":
                case ".jpeg":
                    return "image/jpeg";
                case ".gif":
                    return "image/gif";
                case ".png":
                    return "image/png";
                case ".mp3":
                    return "audio/mpeg";
                case ".mid":
                    return "audio/midi";
                case ".ogg":
                    return "audio/ogg";
                case ".mp4a":
                    return "audio/mp4";
                case ".wav":
                    return "audio/wav";
                case ".wma":
                    return "audio/x-ms-wma";
                case ".avi":
                    return "video/x-msvideo";
                case ".dv":
                    return "video/x-dv";
                case ".mp4":
                    return "video/mp4";
                case ".mpeg":
                case ".mpg":
                    return "video/mpeg";
                case ".mov":
                    return "video/quicktime";
                case ".mkv":
                    return "video/x-matroska";
                case ".zip":
                    return "application/zip";
                case ".rar":
                    return "application/x-rar";
                case ".7z":
                    return "application/x-7z-compressed";
                case ".doc":
                    return "application/vnd.ms-word";
                case ".docx":
                    return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                case ".xls":
                    return "application/vnd.ms-excel";
                case ".xlsx":
                    return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                case ".ppt":
                    return "application/vnd.ms-powerpoint";
                case ".pptx":
                    return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                case ".pdf":
                    return "application/pdf";
                case ".xml":
                    return "application/xml";
                default:
                    throw new RuntimeException("无法识别的后缀格式：" + ext);
            }
        }

        public static HttpFile setNew(Action1T<HttpFile> action) {
            HttpFile file = new HttpFile();
            action.execute(file);
            return file;
        }
    }
}
